//
// Created by Gorun on 2022/5/2.
//

#include "Scanner.h"

extern void error(int line, int column, const std::string& message);

Scanner::Scanner(const std::string& source) {
    this->source=source;

    //add keywords to the map
    keywords.insert(std::pair<std::string, TokenType>("and", AND));
    keywords.insert(std::pair<std::string, TokenType>("class", CLASS));
    keywords.insert(std::pair<std::string, TokenType>("else", ELSE));
    keywords.insert(std::pair<std::string, TokenType>("false", FALSE));
    keywords.insert(std::pair<std::string, TokenType>("for", FOR));
    keywords.insert(std::pair<std::string, TokenType>("fun", FUN));
    keywords.insert(std::pair<std::string, TokenType>("if", IF));
    keywords.insert(std::pair<std::string, TokenType>("nil", NIL));
    keywords.insert(std::pair<std::string, TokenType>("or", OR));
    keywords.insert(std::pair<std::string, TokenType>("print", PRINT));
    keywords.insert(std::pair<std::string, TokenType>("return", RETURN));
    keywords.insert(std::pair<std::string, TokenType>("super", SUPER));
    keywords.insert(std::pair<std::string, TokenType>("this", THIS));
    keywords.insert(std::pair<std::string, TokenType>("true", TRUE));
    keywords.insert(std::pair<std::string, TokenType>("var", VAR));
    keywords.insert(std::pair<std::string, TokenType>("while", WHILE));
}

void Scanner::setSource(const std::string &s) {
    this->source=s;
}

std::string Scanner::getSource() const {
    return source;
}

std::vector<Token> Scanner::scanTokens() {
    while (!isAtEnd()) {
        start = current;
        scanToken();
    }

    tokens.emplace_back(END, "", nullptr, line, column);
    return tokens;
}

void Scanner::scanToken() {
    char c = advance();
    switch (c) {
        case '(': addToken(LEFT_PAREN); break;
        case ')': addToken(RIGHT_PAREN); break;
        case '{': addToken(LEFT_BRACE); break;
        case '}': addToken(RIGHT_BRACE); break;
        case ',': addToken(COMMA); break;
        case '.': addToken(DOT); break;
        case '-': addToken(MINUS); break;
        case '+': addToken(PLUS); break;
        case ';': addToken(SEMICOLON); break;
        case '*': addToken(STAR); break;
        case '!': addToken(match('=') ? BANG_EQUAL : BANG); break;
        case '=': addToken(match('=') ? EQUAL_EQUAL : EQUAL); break;
        case '<': addToken(match('=') ? LESS_EQUAL : LESS); break;
        case '>': addToken(match('=') ? GREATER_EQUAL : GREATER); break;
        case '/':
            if (match('/')) {
                while (peek() != '\n' && !isAtEnd())
                    advance();
            } else {
                addToken(SLASH);
            }
            break;
        case ' ': case '\r': case '\t': break;
        case '\n': line++; column=1; break;
        case '"': string(); break;
        default:
        if (isDigit(c)) {
            number();
        } else if (isAlpha(c)) {
            identifier();
        } else {
            error(line, column, "Unexpected character.");
        }
        break;
    }
}

void Scanner::identifier() {
    while (isAlphaNumberic(peek()))
        advance();

    std::string text = source.substr(start, current-start);
    TokenType type = keywords.count(text)>0?keywords[text]:IDENTIFIER;
    addToken(type);
}

void Scanner::number() {
    while (isDigit(peek()))
        advance();

    if (peek()=='.'&& isDigit(peekNext())) {
        advance();
        while (isDigit(peek()))
            advance();
    }

    std::string text = source.substr(start, current-start);
    auto* number = new Number(std::stod(text));
    addToken(NUMBER, number);
}

void Scanner::string() {
    while (peek()!='"'&&!isAtEnd()) {
        if (peek()=='\n') line++;
        advance();
    }

    if (isAtEnd()) {
        error(line, column, "Unterminated string.");
        return;
    }

    advance();

    std::string value = source.substr(start+1, current-start-2);
    auto* string = new String(value);
    addToken(STRING, string);
}

bool Scanner::match(char expected) {
    if (isAtEnd()) return false;
    if (source[current]!=expected) return false;

    current++;
    return true;
}

char Scanner::peek() {
    if (isAtEnd())
        return '\0';
    else
        return source[current];
}

char Scanner::peekNext() {
    if (current+1>=source.length())
        return '\0';
    else
        return source[current+1];
}

bool Scanner::isAlpha(char ch) {
    return isalpha(ch);
}

bool Scanner::isDigit(char ch) {
    return isdigit(ch);
}

bool Scanner::isAlphaNumberic(char ch) {
    return isAlpha(ch) || isDigit(ch);
}

bool Scanner::isAtEnd() {
    return current>=source.length();
}

char Scanner::advance() {
    column++;
    return source[current++];
}

void Scanner::addToken(TokenType type) {
    addToken(type, nullptr);
}

void Scanner::addToken(TokenType type, const Object* literal) {
    std::string text = source.substr(start, current-start);
    tokens.emplace_back(type, text, literal, line, column);
}